use std::sync::{
  Arc,
  atomic::{AtomicI16, Ordering},
};

use defer_lite::defer;
use log::warn;
use tokio::time::{Duration, sleep};

use crate::{Answer, Error, Result, post};

pub static DOH_LI: &[&str] = &[
  "doh.pub/resolve", // 腾讯
  "dns.google/resolve",
  "dns0.eu",
  "cloudflare-dns.com/dns-query",
  "doh.sb/dns-query",
  "doh.360.cn/resolve",
  "dns.nextdns.io",
  "dns.alidns.com/resolve",
];

pub async fn resolve<T: Send + Unpin + std::fmt::Debug + 'static>(
  name: impl AsRef<str>,
  record_type: impl AsRef<str>,
  extract: impl Fn(&[Answer]) -> Result<Option<T>> + Send + 'static + Clone,
) -> Result<Option<T>> {
  let query = format!("?name={}&type={}", name.as_ref(), record_type.as_ref());
  let (send, recv) = crossfire::mpsc::bounded_async::<Result<T>>(1);

  let spawn = tokio::spawn(async move {
    let ing = Arc::new(AtomicI16::new(DOH_LI.len() as _));

    for doh in riter::iter(DOH_LI) {
      let query = query.clone();
      let send = send.clone();
      let ing = ing.clone();
      let extract = extract.clone();
      tokio::spawn(async move {
        let r = post(doh, &query).await;
        ing.fetch_sub(1, Ordering::Relaxed);

        macro_rules! send_err {
          ($err: expr) => {{
            let runing = ing.load(Ordering::Relaxed);
            if runing >= 0 {
              let err = $err;
              warn!("{runing} : {doh} {err}");
              if runing <= 1 {
                let _ = send.try_send(Err(err));
              }
            }
          }};
        }

        match r {
          Ok(res) => match extract(&res) {
            Ok(res) => {
              if let Some(res) = res {
                let _ = send.try_send(Ok(res));
                ing.store(-1, Ordering::Relaxed);
              }
            }
            Err(err) => send_err!(err),
          },
          Err(err) => {
            send_err!(err);
          }
        }
        Ok::<(), Error>(())
      });
      sleep(Duration::from_millis(500)).await;
    }
    Ok::<(), Error>(())
  });

  defer! {
    spawn.abort();
  }

  match recv.recv().await {
    Ok(r) => Ok(Some(r?)),
    Err(_) => Ok(None),
  }
}
